require 'digest/sha1'
class User < ActiveRecord::Base
  has_and_belongs_to_many :roles
  has_and_belongs_to_many :regions
  
  # Virtual attribute for the unencrypted password
  attr_accessor :password 
  
  validates_presence_of     :name
  validates_presence_of     :email
  validates_presence_of     :password,                   :if => :password_required?
  validates_presence_of     :password_confirmation,      :if => :password_required?
  validates_length_of       :password, :within => 4..40, :if => :password_required?
  validates_confirmation_of :password,                   :if => :password_required?
  validates_length_of       :email,    :within => 3..100
  validates_uniqueness_of   :email, :case_sensitive => false
  before_save :encrypt_password
  
  # prevents a user from submitting a crafted form that bypasses activation
  # anything else you want your user to change should be added here.
  attr_accessible :email, :password, :password_confirmation, :name
  
  acts_as_state_machine :initial => :pending
  state :passive
  state :pending, :enter => :make_activation_code
  state :active,  :enter => :do_activate
  state :suspended
  state :deleted, :enter => :do_delete
    
  def forgot_password
    @forgotten_password = true
    self.make_password_reset_code
  end
  
  def reset_password
    # First update the password_reset_code before setting the 
    # reset_password flag to avoid duplicate email notifications.
    update_attributes(:password_reset_code => nil)
    @reset_password = true
  end  
  
  event :register do
    transitions :from => :passive, :to => :pending, :guard => Proc.new {|u| !(u.crypted_password.blank? && u.password.blank?) }
  end
  
  event :activate do
    transitions :from => :pending, :to => :active 
  end
  
  event :suspend do
    transitions :from => [:passive, :pending, :active], :to => :suspended
  end
  
  event :delete do
    transitions :from => [:passive, :pending, :active, :suspended], :to => :deleted
  end
  
  event :unsuspend do
    transitions :from => :suspended, :to => :active,  :guard => Proc.new {|u| !u.activated_at.blank? }
    transitions :from => :suspended, :to => :pending, :guard => Proc.new {|u| !u.activation_code.blank? }
    transitions :from => :suspended, :to => :passive
  end
  
  # Authenticates a user by their e-mail name and unencrypted password.  Returns the user or nil.
  def self.authenticate(email, password)
    u = find_in_state :first, :active, :conditions => {:email => email} # need to get the salt
    u && u.authenticated?(password) ? u : nil
  end
  
  # Encrypts some data with the salt.
  def self.encrypt(password, salt)
    Digest::SHA1.hexdigest("--#{salt}--#{password}--")
  end
  
  # Encrypts the password with the user salt
  def encrypt(password)
    self.class.encrypt(password, salt)
  end
  
  def authenticated?(password)
    crypted_password == encrypt(password)
  end
  
  def remember_token?
    remember_token_expires_at && Time.now.utc < remember_token_expires_at 
  end
  
  # These create and unset the fields required for remembering users between browser closes
  def remember_me
    remember_me_for 2.weeks
  end
  
  def remember_me_for(time)
    remember_me_until time.from_now.utc
  end
  
  def remember_me_until(time)
    self.remember_token_expires_at = time
    self.remember_token            = encrypt("#{email}--#{remember_token_expires_at}")
    save(false)
  end
  
  def forget_me
    self.remember_token_expires_at = nil
    self.remember_token            = nil
    save(false)
  end
  
  #used in user_observer
  def recently_forgot_password?
    @forgotten_password
  end
  
  def recently_reset_password?
    @reset_password
  end
  
  # Returns true if the user has just been activated.
  def recently_activated?
    @activated
  end
  
  # has_role? simply needs to return true or false whether a user has a role or not.  
  # It may be a good idea to have "admin" roles return true always
  def has_role?(role_in_question)
    @_list ||= self.roles.collect(&:name)
    return true if @_list.include?("superadmin")
    (@_list.include?(role_in_question.to_s) )
  end
  
  def has_region?(region)
    return true if self.roles.collect(&:name).include?(region.to_s)
  end
  
  def add_role(role)
    if !self.has_role?(role) 
      self.roles << role
      self.save
    end
  end
  
  def add_region(region)
    if !self.has_region?(region)
      self.regions << region
      self.save
    end
  end
  
  def remove_role(role)
    self.roles.delete role
    self.save
  end
  
  def remove_region(region)
    self.regions.delete region
    self.save
  end
  
  def add_region_admin(region)
    if !self.has_role?("admin")
      self.add_role(Role.find_by_name("admin"))
    end
    self.add_region(region)
  end
  
  def remove_region_admin(region)
    self.remove_region(region)
    if self.regions.length == 0
      self.remove_role(Role.find_by_name("admin"))
    end
  end
  
  protected
  # before filter 
  def encrypt_password
    return if password.blank?
    self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{email}--") if new_record?
    self.crypted_password = encrypt(password)
  end
  
  def password_required?
    crypted_password.blank? || !password.blank?
  end
  
  def make_activation_code
    self.deleted_at = nil
    self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
  end
  
  def do_delete
    self.deleted_at = Time.now.utc
  end
  
  def do_activate
    @activated = true
    self.activated_at = Time.now.utc
    self.deleted_at = self.activation_code = nil
  end  
  
  # Valid group options are :admins, :superadmins, :admins_and_superadmins
  # :users, and :customers
  def self.get_all group
    case group
      when :users
        User.find(:all)
      when :customers
        User.find(:all).reject {|u| u.roles.select {|r| r.name == 'admin' || r.name == 'superadmin' }.size > 0 }
      when :admins
        User.find(:all).select {|u| u.roles.select {|r| r.name == 'admin' }.size > 0 }
      when :superadmins
        User.find(:all).select {|u| u.roles.select {|r| r.name == 'superadmin' }.size > 0 }
      when :admins_and_superadmins
        User.find(:all).select {|u| u.roles.select {|r| r.name == 'admin' || r.name == 'superadmin' }.size > 0 }
      default
        raise "invalid group called"
    end
  end
end
